#include <iostream>
#include "grammar.hpp"
#include <AST.hpp>
#include <global.hpp>
#include <llvm/ADT/SmallVector.h>
#include <llvm/IR/DerivedTypes.h>
#include <YY/type.hpp>
#include <graphviz.hpp>
#define _GLIBCXX_INCLUDE_NEXT_C_HEADERS
#include <math.h>
#define _GLIBCXX_INCLUDE_NEXT_C_HEADERS
#include <stdio.h>
#define _GLIBCXX_INCLUDE_NEXT_C_HEADERS
#include <stdlib.h>

#ifdef _WIN32
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT
#endif

llvm::LLVMContext theContext;
llvm::IRBuilder<> builder(theContext);
llvm::Module theModule("fajal", theContext);
llvm::legacy::FunctionPassManager theFPM(&theModule);
Scope theScope;

static llvm::FunctionCallee printfCallee;
static llvm::FunctionCallee scanfCallee;
static llvm::FunctionCallee gets_sCallee;

using namespace llvm;
using namespace std;

#ifndef BAN_BACKEND
extern void init_llvm(bool shouldEnableAllPass);
extern void emit_llvm();
#endif

void init_libc();
void test_llvm();
void test0(), test1(), test2(), test3(), test4();
void test5(), test6(), test7(), test8(), test9();
void test10(), test11();

Expr *program = nullptr;

int main(int argc, char **argv) {
#if YYDEBUG
    yydebug = 1;
#endif // YYDEBUG
    if (argc > 1) {
        freopen(argv[1], "r", stdin);
    } else {
        cout << "looking for test.kt...\n";
        freopen("test.kt", "r", stdin);
    }
    yyparse();

    // 硬编码：最终要删除
    theModule.setTargetTriple("x86_64-unknown-linux-gnu");
    theModule.setDataLayout(
        "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128");

#ifndef BAN_BACKEND
    init_llvm(true);
#endif
    init_libc();
    // program->codegen_print();
    test_llvm();
#ifndef BAN_BACKEND
    emit_llvm();
#endif
    return 0;
}

void test_llvm() {
    // test0();
    // test1();
    // test2();
    // test3();
    // test4();
    // test5();
    // test6();
    // test7();
    // test8();
    // test9();
    // test10();
    // test11();
    if (program) program->codegen_print();
    cerr << endl;
    cerr << "---------theModule---------\n";
    theModule.print(errs(), nullptr, false, true);
    auto& graph = graphviz::GraphGenerator::get();
    program->accept(graph);
}

// 结构体
void test11() {
    using namespace std;
    PrototypeAST *mainProto =
        new PrototypeAST("main", {}, {}, {}, BasicType::IntTy);
    Var *x = new Var("x", true, false, false, nullptr, BasicType::IntTy);
    Var *y = new Var("y", true, false, false, nullptr, BasicType::IntTy);
    Struct *def = new Struct("Vector", {x, y});
    // Var * v = new Var("vector", true, false, false, nullptr, def->getType());

    Var *v = new Var(
        "vector", true, false, false, nullptr, theScope.findType("Vector"));

    DotAST *deRefx = new DotAST(new Var("vector"), new Var("x"));
    Expr *assign = BinaryOp::assign(deRefx, new Int(5));
    Expr *body = Expr::sequenceOf({v, assign, new Return(new Int(0))});
    FunctionAST *mainFun = new FunctionAST(mainProto, body);
    mainFun->codegen_print();
}

// 斐波那契数列
void test10() {
    using namespace std;
    PrototypeAST *mainProto = new PrototypeAST(
        "fib", {BasicType::IntTy}, {"x"}, {false}, BasicType::IntTy);
    Expr *m1 = new BinaryOp(OpType::Sub, new Var("x"), new Int(1));
    Expr *m2 = new BinaryOp(OpType::Sub, new Var("x"), new Int(2));
    If *cond = new If(
        new BinaryOp(OpType::Lt, new Var("x"), new Int(2)), new Int(1),
        new BinaryOp(
            OpType::Add, new Call("fib", {m1}), new Call("fib", {m2})));
    Expr *body = Expr::sequenceOf({cond});
    FunctionAST *f = new FunctionAST(mainProto, body);
    f->codegen_print();
}

// Array of Char
void test9() {
    PrototypeAST *mainProto =
        new PrototypeAST("_main_", {}, {}, {}, BasicType::VoidTy);
    Expr *defArr = new ArrayPtr(
        "str", true, true, new StringPtr("Hello String!\n"), false,
        BasicType::CharTy);
    Expr *defVar = new Var("x", true, false, false, new Int(0), nullptr);
    // clang-format off
    Expr *whileLoop = new While(
        new BinaryOp(OpType::Lt, new Var("x"), new Int(10)),
        Expr::sequenceOf({
            BinaryOp::assign(new ArrayPtr("str", new Var("x")), new Char('F')),
            new Call("printf", {new StringPtr("%c1 "), new ArrayPtr("str", new Var("x"))}),
            new BinaryOp(OpType::Assign, new Var("x"), new BinaryOp(OpType::Add, new Var("x"), new Int(1)))
        })
    );
    // clang-format on
    Expr *body = Expr::sequenceOf({defArr, defVar, whileLoop});
    FunctionAST *f = new FunctionAST(mainProto, body);
    f->codegen_print();
}

// while loop
void test8() {
    PrototypeAST *mainProto =
        new PrototypeAST("_main_", {}, {}, {}, BasicType::VoidTy);
    // clang-format off
    Expr *defVar = new Var("x", true, false,  false, new Int(0),nullptr);
    Expr *whileLoop = new While(
        new BinaryOp(OpType::Lt, new Var("x"), new Int(150)),
        Expr::sequenceOf({
            new Call("printf", {new StringPtr("%d "), new Var("x")}),
            new BinaryOp(OpType::Assign, new Var("x"), new BinaryOp(OpType::Add, new Var("x"), new Int(1)))
        })
    );
    // clang-format on
    Expr *body = Expr::sequenceOf({defVar, whileLoop});
    FunctionAST *f = new FunctionAST(mainProto, body);
    f->codegen_print();
}

// for loop
void test7() {
    PrototypeAST *mainProto =
        new PrototypeAST("_main_", {}, {}, {}, BasicType::VoidTy);
    // clang-format off
    Expr *forLoop = new For(
        new Var("x", true, false,  false, new Int(123),nullptr),
        new BinaryOp(OpType::Lt, new Var("x"), new Int(150)),
        new BinaryOp(OpType::Assign, new Var("x"), new BinaryOp(OpType::Add, new Var("x"), new Int(1))),
        new Call("printf", {new StringPtr("%d "), new Var("x")})
    );
    // clang-format on
    Expr *body = Expr::sequenceOf({forLoop});
    FunctionAST *f = new FunctionAST(mainProto, body);
    f->codegen_print();
}

// scanf
void test6() {
    SmallVector<Type *> paraTypes = {BasicType::IntTy};
    SmallVector<string> paraNames = {"x"};
    SmallVector<bool> paraIsRefs = {false};
    PrototypeAST *mainProto = new PrototypeAST(
        "_main_", paraTypes, paraNames, paraIsRefs, BasicType::IntTy);
    Expr *defVar = new Var("y", true, false, false, new Int(0), nullptr);
    SmallVector<Expr *> paras_0{new StringPtr("%d"), new Var("y")};
    Expr *call_0 = new Call("scanf", paras_0);
    Expr *plus = new BinaryOp(OpType::Mul, new Var("x"), new Var("y"));
    Expr *assign = new BinaryOp(OpType::Assign, new Var("x"), plus);
    Expr *call_1 =
        new Call("printf", {new StringPtr("Sum = %d\n"), new Var("x")});
    Expr *body = Expr::sequenceOf({defVar, call_0, assign, call_1});
    FunctionAST *f = new FunctionAST(mainProto, body);
    f->codegen_print();
}

// 按引用传递
void test5() {
    SmallVector<Type *> paraTypes = {BasicType::IntTy};
    SmallVector<string> paraNames = {"x"};
    SmallVector<bool> paraIsRefs = {true};
    PrototypeAST *mainProto = new PrototypeAST(
        "_main_", paraTypes, paraNames, paraIsRefs, BasicType::IntTy);
    SmallVector<Expr *> paras{new StringPtr("Origin: %d\n"), new Var("x")};
    Expr *call_0 = new Call("printf", paras);
    Expr *assign = new BinaryOp(OpType::Assign, new Var("x"), new Int(666));
    Expr *call_1 = new Call("printf", paras);
    Expr *body = Expr::sequenceOf({call_0, assign, call_1});
    FunctionAST *f = new FunctionAST(mainProto, body);
    f->codegen_print();
}

// extern input output
void test4() {
    PrototypeAST *mainProto =
        new PrototypeAST("_main_", {}, {}, {}, BasicType::VoidTy);
    SmallVector<Expr *> paras{new StringPtr("Hello World!\n")};
    Expr *call = new Call("printf", paras);
    FunctionAST *f = new FunctionAST(mainProto, call);
    f->codegen_print();
}

// extern cos
void test3() {
    SmallVector<Type *> paraTypes = {BasicType::FloatTy};
    SmallVector<string> paraNames = {"x"};
    SmallVector<bool> paraIsRefs = {false};
    PrototypeAST *proto = new PrototypeAST(
        "cos", paraTypes, paraNames, paraIsRefs, BasicType::FloatTy);
    PrototypeAST *mainProto = new PrototypeAST(
        "_main_", paraTypes, paraNames, paraIsRefs, BasicType::FloatTy);
    Var *para = new Var("x");
    Expr *call = new Call("cos", {para});
    FunctionAST *f = new FunctionAST(mainProto, call);
    Expr *all = Expr::sequenceOf({proto, f});
    all->codegen_print();
}

void test2() {
    SmallVector<Type *> paraTypes = {BasicType::IntTy};
    SmallVector<string> paraNames = {"x"};
    SmallVector<bool> paraIsRefs = {false};
    PrototypeAST *proto = new PrototypeAST(
        "assign_Add_666", paraTypes, paraNames, paraIsRefs, BasicType::IntTy);
    Var *x = new Var("x");
    Expr *plus = new BinaryOp(OpType::Add, x, new Int(666));
    Var *user = new Var("user", true, false, false, nullptr, BasicType::IntTy);
    Expr *assign = new BinaryOp(OpType::Assign, user, plus);
    Expr *body = Expr::sequenceOf({user, assign});
    FunctionAST *f = new FunctionAST(proto, body);
    f->codegen_print();
}

void test1() {
    SmallVector<Type *> paraTypes = {BasicType::IntTy};
    SmallVector<string> paraNames = {"x1"};
    SmallVector<bool> paraIsRefs = {false};
    PrototypeAST *proto = new PrototypeAST(
        "add1", paraTypes, paraNames, paraIsRefs, BasicType::IntTy);
    Var *x1 = new Var("x1");
    Expr *plus = new BinaryOp(OpType::Add, x1, new Int(1));
    Expr *br = new If(
        new BinaryOp(OpType::Geq, plus, new Int(100)),
        new BinaryOp(OpType::Mul, x1, new Int(2)),
        new BinaryOp(OpType::Div, x1, new Int(3)));
    FunctionAST *f =
        new FunctionAST(proto, new BinaryOp(OpType::Semi, plus, br));

    f->codegen_print();
}

void test0() {
    Expr *a = new Char('a');
    a->codegen_print();
    cerr << endl;
    Expr *b = new Int(666);
    b->codegen_print();
    cerr << endl;
    Expr *plus = new BinaryOp(OpType::Add, a, b);
    plus->codegen_print();
}

void init_libc() {
    printfCallee = theModule.getOrInsertFunction(
        "printf", FunctionType::get(
                      IntegerType::getInt32Ty(theContext),
                      PointerType::get(Type::getInt8Ty(theContext), 0),
                      true /* this is var arg func type*/));
    scanfCallee = theModule.getOrInsertFunction(
        "scanf", FunctionType::get(
                     IntegerType::getInt32Ty(theContext),
                     PointerType::get(Type::getInt8Ty(theContext), 0),
                     true /* this is var arg func type*/));
    gets_sCallee = theModule.getOrInsertFunction(
        "gets", FunctionType::get(
                    Type::getInt8PtrTy(theContext, 0),
                    {Type::getInt8PtrTy(theContext, 0)},
                    false /* this is var arg func type*/));
}